/*
 *
 *  Created by John David Duncan on 4/27/09.
 *  Copyright 2009 Sun Microsystems. All rights reserved.
 *
 */

/*
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; version 2 of the License.
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.
 
 You should have received a copy of the GNU General Public License
 along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */

#include "SQLnode.h"
#include "Variable.h"
#include "innodb.h"
#include <stdarg.h>
#include <assert.h>

/* externs */
extern char * default_db;    // from main.cpp
extern "C" int describe_table(const char *);
extern "C" void describe_col_type(ib_col_type_t, ib_u32_t, ib_col_attr_t);

/* statics */
bool innobase_is_started = false;
const char *constants[sql_const_max];
const char *tool_errors[e_MAX];

/* function prototypes */
void show_help(short);
int set_var(SQLnode *);
void print_col(ib_tpl_t &, int);

/* implementations */

void init_constants() {
  int topline = __LINE__;
  constants[0]                = "";
  constants[obj_Table]        = "TABLE";
  constants[obj_Database]     = "DATABASE";
  constants[obj_Column]       = "COLUMN";
  constants[obj_Index]        = "INDEX";
  constants[obj_Var]          = "VARIABLE";
  constants[col_VARCHAR]      = "VARCHAR";
  constants[col_CHAR]         = "CHAR";
  constants[col_BLOB]         = "BLOB";
  constants[col_INT]          = "INT";
  constants[col_FLOAT]        = "FLOAT";
  constants[col_DOUBLE]       = "DOUBLE";
  constants[idx_index]        = "INDEX";
  constants[idx_unique]       = "UNIQUE INDEX";
  constants[idx_primary]      = "PRIMARY KEY";
  constants[op_create]        = "CREATE";
  constants[op_alter]         = "ALTER";
  constants[op_add]           = "ADD";
  constants[op_drop]          = "DROP";
  constants[op_truncate]      = "TRUNCATE";
  constants[op_describe]      = "DESCRIBE";
  constants[op_help]          = "HELP";
  constants[op_show]          = "SHOW";
  constants[op_start]         = "START";
  constants[op_stop]          = "STOP";
  constants[op_set]           = "SET";
  assert(__LINE__ - topline - 1 == sql_const_max);

  /* A preprocessor trick: */
#define TOOLERR(a,b) tool_errors[a - e_BASE_VALUE] = b;
#include "errors.map"
#undef TOOLERR
}

void show_error(int err) {
  if(err < e_BASE_VALUE) 
    printf("InnoDB Error [%d]: %s \n", err, ib_strerror((ib_err_t) err));
  else printf("Error %d: %s \n", err, tool_errors[err - e_BASE_VALUE]);
}


void report(char **p_start, const char *p_end, const char *fmt, ... ) {
  va_list args;
  int max_len;
    
  va_start(args,fmt);
  max_len = p_end - *p_start; 
  if(max_len > 0) 
    *p_start += vsnprintf(*p_start, max_len, fmt, args);
  va_end(args);
}


void print_tuple_meta(ib_tpl_t &tuple) {
  ib_col_meta_t col_meta; 
  int ncols = ib_tuple_get_n_cols(tuple);

  for(int i = 0 ; i < ncols ; i++) {
    ib_ulint_t data_len = ib_col_get_meta(tuple, i, &col_meta); 

    printf("Col %d: ",i);
    describe_col_type(col_meta.type, data_len, col_meta.attr);
    printf("\n");
  }
}

void print_col(ib_tpl_t &tpl, int ncol) {
  ib_u32_t val_int;
  float val_float;
  double val_double;
  const char * val_ptr;
    
  ib_col_meta_t col_meta; 
  ib_ulint_t    data_len; 

  data_len = ib_col_get_meta(tpl, ncol, &col_meta); 
  if (data_len == IB_SQL_NULL) { 
    printf("[null]"); 
    return; 
  } 

  switch (col_meta.type) { 
    case IB_INT: 
      switch (col_meta.type_len) { 
        case 4:
          ib_tuple_read_u32(tpl, ncol, &val_int); 
          printf( "%u", val_int); 
          break;

        case 1: case 2: case 8:
        default: 
          printf(" ?? ");
      } 
      break; 
  
    case IB_FLOAT:
      ib_tuple_read_float(tpl, ncol, &val_float); 
      printf("%f", val_float); 
      break; 
    
    case IB_DOUBLE: 
      ib_tuple_read_double(tpl, ncol, &val_double); 
      printf("%lf", val_double); 
      break; 
     
    case IB_BLOB: 
    case IB_VARCHAR: 
    case 4:
      /* Get access to the raw data via a pointer. */ 
      val_ptr = (const char *) ib_col_get_value(tpl, ncol); 
      printf("%.*s", (int) data_len, val_ptr); 
      break; 

    case IB_SYS:
      printf("[sys]");
      break;
      
    default: 
      printf( "???");
      break; 
  } 
}


// GENERIC NODE 
void SQLnode::Dump(char **p, const char *s) {
  report(p, s, "%s %s %s;", constants[op], constants[type], name());
  
  /* Special case: actually set variables, even in "dump" mode.
     (otherwise there is no way to get back to "run" mode.)          */
  if(op == op_set && type == obj_Var) {
    int err = Run();
    if(err != DB_SUCCESS) show_error(err);  
  }
}

int SQLnode::Run() {  // HELP, START/STOP, CREATE DATABASE, DROP DATABASE
  ib_err_t err = DB_SUCCESS;
  
  switch(op) {
    case op_help:
      show_help(type);
      break;
    case op_start:
      err = ib_startup("Barracuda");
      innobase_is_started = true;
      break;
    case op_stop:
      err = ib_shutdown();
      innobase_is_started = false;
      ib_init();  // start anew
      break;
    case op_create:
      if(ib_database_create(sch) == IB_TRUE) 
        printf("DATABASE %s CREATED\n",sch);
      else
        printf("CREATE DATABASE %s FAILED\n",sch);
      break;
    case op_drop:
      err = ib_database_drop(sch); 
      if(err == DB_SUCCESS) printf("Database %s dropped.\n",sch);
      break;
    case op_set:
      return set_var(this);
    default:
      printf("Not implemented! %s %d \n", __FILE__,__LINE__);
  }
  return (int) err;
}


// SQLshow: SHOW variable , SHOW TABLES 

int SQLshow::Run() {
  switch(op) {
    case op_show:
      if(type == obj_Table) 
        return show_tables();
      else if(type == obj_Var) 
        return show_var(this);
      else assert(0);
      break;
    case op_set:
    default:
      break;
  }
  return DB_SUCCESS;
}


extern "C" int table_name_visitor(void *a, const char *name, int x) {
  for(const char *c = name ; *c != 0 ; c++)
    if(*c == '/') putchar('.');
      else putchar(*c);
  putchar('\n');
  return 0;
}


int SQLshow::show_tables() {  
  if(! innobase_is_started) return e_START_THE_SERVER;
  
  /* Hard coded: show the data dictionary tables */
  printf("SYS_TABLES\n");
  printf("SYS_COLUMNS\n");
  printf("SYS_INDEXES\n");
  printf("SYS_FIELDS\n");

  ib_trx_t trx = ib_trx_begin(IB_TRX_SERIALIZABLE);  
  ib_err_t err = ib_schema_lock_exclusive(trx);
  if(err == DB_SUCCESS);
    err = ib_schema_tables_iterate(trx, table_name_visitor, 0);  
  ib_trx_commit(trx);
  return err;
}


QualifiedName::QualifiedName(char *scope, char *obj, char div) {
  if(scope && scope[0]) {
    size_t len = strlen(scope) + strlen(obj) + 2;
    _name = new char[len];
    snprintf(_name, len, "%s%c%s", scope, div, obj);
  }
  else _name = obj;
}


void show_help(short help_op) {

  const char *help = 
    "     ddltool for embedded innodb \n\n"
    "USE database_name \n"
    "SHOW TABLES \n"
    "DESCRIBE table_name \n"
    "START DATABASE \n"
    "STOP DATABASE \n"
    "QUIT \n"
    "DROP ( DATABASE | TABLE ) name \n"
    "\n"
    " --- for more information see : ---\n"
    "   HELP CREATE \n"
    "   HELP ALTER \n"
    "   HELP SET \n";

  const char *set_help = 
    "SHOW VARIABLES \n"
    "SHOW ib.variable \n"
    "SHOW tool.variable \n"
    "SET ib.variable = value                 Set an InnoDB configuration value \n"
    "SET tool.mode = (run | dump | test)     Set ddltool's operating mode \n" 
    "SET tool.hidden = 1                     Show InnoDB system columns from tables \n"
    "SET tool.db = database                  Set the default database \n"
    "USE db                                  Same as SET tool.db = \"db\" \n";

  const char *create_help =   
    "CREATE DATABASE db_name ;\n\n"
    "CREATE TABLE table_name ( <table_attr> , ... ) [<table_option> , ... ]\n" 
    "   table_attr: [ <column_defn> | <index_defn> ]\n"
    "   table_attr: column_name <datatype> [(size)] [ NOT NULL | PRIMARY KEY ]\n"
    "   datatype:  VARCHAR | CHAR | BLOB | INT | FLOAT | DOUBLE\n"
    "   index_defn: ([UNIQUE] INDEX [index_name] | PRIMARY KEY) { col_name(size) , ... }\n"
    "   table_option:  KEY_BLOCK_SIZE = [1,2,4,8,16] \n"
    "                  ROW_FORMAT = [ REDUNDANT | COMPACT | DYNAMIC | COMPRESSED ] \n";

  const char *alter_help = 
    "ALTER TABLE [ db_name \".\" ] table_name <table_change> \n"
    "   table_change:  <add_index> | <drop_index> \n"
    "   add_index:  ADD [UNIQUE] INDEX [index_name] { col_name [(size)] , ... }\n"
    "   drop_index:  DROP INDEX index_name\n";
  
  if(help_op == op_create) puts(create_help);
  else if(help_op == op_set) puts(set_help);
  else if(help_op == op_alter) puts(alter_help);
  else puts(help);
}
